#!/usr/bin/env python
import os, sys, re

SUPPORTED_LANG = ['cpp', 'python'] #no plan to support java, maybe support go later
DEFAULT_LANG = SUPPORTED_LANG[0]
KEYWORDS = """class sequence enum true false import interface session server client""".split()
BUILTIN_TYPES = """double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes""".split()
LANGUAGE_KEYWORDS = "class struct operator ...".split() # C++ and python keywords list
SORB_EXT = '.si'
PB_EXT = '.proto'
INTERFACE_EXT = '.sii'
PB_HEADER_EXT = '.pb.h'
PB_CPP_EXT = '.pb.cc'
INTERFACE_HEADER_EXT = 'i.h'
INTERFACE_CPP_EXT = 'i.cc'
DEBUG = False


class ArgParser(object) :
    def parse(self) :
        import argparse

        class OutDirAction(argparse.Action):
            def __call__(self, parser, namespace, values, option_string=None):
                outdir_abs = os.path.abspath(values)
                if os.path.isdir(outdir_abs) :
                    setattr(namespace, self.dest, outdir_abs)
                else :
                    print '%s %s: directory can not be found!' % (option_string, outdir_abs)
                    parser.print_help()
                    sys.exit(1)

        class FileAction(argparse.Action):
            def __call__(self, parser, namespace, values, option_string=None):
                file_abs = os.path.abspath(values)
                if not file_abs.endswith(SORB_EXT) :
                    print '%s %s: invalide si file extention name, must ends with .si' % (option_string, file_abs)
                    parser.print_help()
                    sys.exit(1)

                if os.path.isfile(file_abs) :
                    setattr(namespace, self.dest, file_abs)
                    setattr(namespace, 'search', os.path.dirname(file_abs))
                else :
                    print '%s %s: input file does not exist' % (option_string, file_abs)
                    parser.print_help()
                    sys.exit(1)

        parser = argparse.ArgumentParser()
        parser.add_argument('-f', '--file', required=True, action=FileAction)
        parser.add_argument('-o', '--output_dir', default='.', action=OutDirAction)
        parser.add_argument('-g', '--generate', choices=SUPPORTED_LANG, default=DEFAULT_LANG)

        self.args=parser.parse_args()

    def __getattr__(self, attr) :
        return getattr(self.args, attr)

class SORBModulesPool(object) :
    def __init__(self) :
        self.module_map = {}

    def append(self, module_name, module = None) :
        if module :
            self.module_map[module_name] = module
        else :
            if not module_name in self.module_map :
                self.module_map[module_name] = SorbIDL(module_name)
                self.module_map[module_name].parse()
        return self.module_map[module_name]

    def gen_code(self) :
        for i in self.module_map.values() :
            i.write_IDL()
        for i in self.module_map.values() :
            i.gen_code()


class RedefinedSymbol(RuntimeError) :
    pass

class SymbolNotFound(RuntimeError) :
    pass

class TypeProvider:
    def __init__(self) :
        self.types = {}
        self.unresolved = []

    def add_type(self, type):
        if type in KEYWORDS + BUILTIN_TYPES + LANGUAGE_KEYWORDS :
            raise RedefinedSymbol('E: symbol name "%s" conflict with the keywords' % type)
        if type.id() in self.types :
            raise RedefinedSymbol('E: type "%s" has been defined earlier' % type.id())
        self.types[type.id()] = type

    def resolve(self, type_id, type_of_type = None, value = None, external = False):
        if type_id in self.types :
            type = self.types[type_id]
            if value :
                if type.type() != 'enum' :
                    return False
                if type.defined(value) :
                    return True
                else :
                    return False
            if type_of_type :
                return type_of_type == type.type()
            return True
        else :
            if not external :
                self.unresolved.append(dict(type_id = type_id, value = value, type_of_type = type_of_type))
            return False

    def resolve_imports(self):
        #fixme m.type() should be a module name
        imports = dict([(m.type(), m) for m in [i.get_modules() for i in self.types.values() if i.type() == 'import']])
        for i in self.unresolved :
            type, type_of_type, value = i
            if '.' in type :
                module_name, imported_type = type.split('.', 1)
                if module_name in imports :
                    if not imports[module_name].resolve(imported_type, type_of_type, value, True) :
                        raise SymbolNotFound('E: Symbol %s is not found in the imported module' % type)
                else :
                    raise SymbolNotFound('E: Can not find module %s with %s' % (module_name,type))

class TypeConsumer:
    def __init__(self, parent) :
        self.parent = parent
        self.context = {}

    def check_symbol(self, symbol, type_id, value = None):
        if symbol in KEYWORDS + BUILTIN_TYPES + LANGUAGE_KEYWORDS :
            raise RedefinedSymbol('E: symbol name "%s" conflict with the keywords' % symbol)
        if symbol in self.context :
            raise RedefinedSymbol('E: symbol "%s" has been defined earlier' % symbol)
        self.context[symbol] = type_id
        self.parent.resolve(type_id, None, value)

    def check_type(self, type_id):
        if type_id in BUILTIN_TYPES :
            return
        if type_id in KEYWORDS + LANGUAGE_KEYWORDS :
            raise RedefinedSymbol('E: type name "%s" conflict with the keywords' % type_id)
        self.parent.resolve(type_id)

    def check_type_of_type(self, type, type_of_type):
        if type in KEYWORDS + BUILTIN_TYPES + LANGUAGE_KEYWORDS :
            raise RedefinedSymbol('E: type name "%s" conflict with the keywords' % type)
        if type in self.context :
            raise RedefinedSymbol('E: type "%s" has been defined earlier' % type)
        self.context[type] = type_of_type
        self.parent.resolve(type, type_of_type)

    def contains(self, symbol):
        return symbol in self.context

#global variable
arg_parser = ArgParser()
MODULES = SORBModulesPool()

class OrderCounter(object) :
    def __init__(self, init=0) :
        self.__counter = init

    def increase(self) :
        self.__counter += 1
        return self.__counter

class Import(object) :
    signature = 'import'
    separator = ';'

    def __init__(self, initList) :
        # initList should like ['import', ['test', 'test1']]
        self.__modules = {}
        self.__values = sorted(list(set(initList[1])))

    def id(self):
        return ','.join(self.__values)

    def type(self) :
        return self.signature

    def parse(self) :
        # remove the duplicated modules and parse them
        for i in self.__values :
            self.__modules[i] = MODULES.append(i)
            #self.__modules[i].parse()

    def get_modules(self):
        return self.__modules.values()

    @classmethod
    def get_instance(klass, initList) :
        if initList[0] == klass.signature:
            obj = klass(initList)
            return obj
        else :
            return None

    def pb(self) :
        return '\n'.join(['import "%s.proto"%s' % (i, self.separator) for i in self.__modules.keys() ]) + '\n'

    def si(self):
        return 'import %s%s\n' %  (','.join(self.__modules.keys()), self.separator)

    def python(self):
        return '\n'.join(['import %s_pb2 as %s' % i for i in self.__modules.keys()])

    def cpp(self):
        pass

class SorbElementError(RuntimeError) :
    pass

class SorbElement(object) :
    signature = 'sorb_base_signature'
    #line separator
    separator = ';'
    #shift width
    sw = ' ' * 4

    def __init__(self, initList, parent=None) :
        if len(initList) == 3 :
            self.__type, self.__id, self.values = initList
        else :
            raise SorbElementError('Bad init list format, length should be 3: %s ' % str(initList))

        self.__init_list = initList
        self.parent = parent

    def id(self) :
        return self.__id

    def type(self):
        return self.signature

    def label(self) :
        return '%s %s' % (self.signature, self.__id)

    def parse(self) :
        raise NotImplementedError()

    @classmethod
    def get_instance(klass, initList, parent) :
        #print 'SorbElement get_instance ', klass.__name__
        if initList[0] == klass.signature:
            obj = klass(initList, parent)
            return obj
        else :
            return None

    @staticmethod
    def shift(content, level = 1) :
        rtv=[]
        for i in content :
            tmp = []
            for j in i.split('\n') :
                tmp.append('%s%s' % (SorbElement.sw * level, j))
            rtv.append('\n'.join(tmp))
        return rtv

    def pb(self):
        return ''

    def si(self):
        return ''

    def python(self):
        return ''

    def cpp(self):
        return ''


class EnumError(RuntimeError) :
    pass

class Enum(SorbElement) :
    signature = 'enum'

    def __init__(self, initList, parent=None) :
        SorbElement.__init__(self, initList, parent)
        #the enum should not have sub-type to emit a up search, so the parent is None
        self.symbol_keeper = TypeConsumer(parent.symbol_keeper)
        for i in self.values :
            #the enum value should not be a type and with builtin type 'int32'
            self.symbol_keeper.check_symbol(i, self.id())

    def defined(self, value):
        return self.symbol_keeper.contains(value)

    def pb(self) :
        # the enum order starts from 0, put -1 here.
        counter = OrderCounter(-1)
        return '''\
%s {
%s
}
''' % (self.label(), '\n'.join(SorbElement.shift(['%(key)s = %(value)s%(separator)s' % dict(key=k, value=counter.increase(), separator=SorbElement.separator) for k in self.values])))

    def si(self):
        return '''\
%s {
%s
}
''' % (self.label(), '\n'.join(SorbElement.shift(['%(key)s%(separator)s' % dict(key=k, separator=SorbElement.separator) for k in self.values])))

    def python(self):
        #python enum doesn't need to generate additional code
        return ''

    def cpp(self):
        pass

class SessionError(RuntimeError) :
    pass

class Session(SorbElement) :
    signature = 'session'

    def __init__(self, initList, parent) :
        SorbElement.__init__(self, initList, parent)
        #the enum should not have sub-type to emit a up search, so the parent is None
        self._members = []
        self.symbol_keeper = TypeConsumer(parent.symbol_keeper)
        for i in self.values :
            type_of_type, type = i
            if type_of_type in ['class' ,'interface'] :
                self._members.append((type_of_type, type))
                self.symbol_keeper.check_type_of_type(type, type_of_type)
            else :
                raise SessionError('Bad init list format, only accept class and interface: %s ' % str(initList))
            #should I resolve class and interface here?

    def si(self) :
        return '''\
%s {
%s
}
''' % (self.label(), '\n'.join(SorbElement.shift(['%(type)s %(value)s%(separator)s' % dict(type=m[0], value=m[1], separator=SorbElement.separator) for m in self._members])))

    def python(self) :
        pass

class FactoryError(RuntimeError):
    pass

class Factory(SorbElement) :
    signature = 'factory'

    def __init__(self, initList, parent) :
        SorbElement.__init__(self, initList, parent)
        self.symbol_keeper = TypeConsumer(parent.symbol_keeper)
        self.session = None
        self.methodList = []
        for i in self.values :
                #insert method to the list
                method = MemberMethod.get_instance(i)
                if method :
                    self.methodList.append(method)
                    self.symbol_keeper.check_symbol(method.id(), 'func')
                    #should I resolve functions syntax here?
                else :
                    if i[0] == 'session' :
                        if self.session != None :
                            raise FactoryError('More than one session defined in %s %s' % (self.signature,self.id()))
                        else :
                            self.session = i[1]
                            self.symbol_keeper.check_type_of_type(i[1], 'session')
                    else :
                        raise ParseMemberMethodError('Bad method format of %s %s' % (self.signature, self.id()))

    def si(self) :
        return '''\
%s {
%s
}
''' % (self.label(), '%s' % ('\n'.join(SorbElement.shift(['session %s%s' %(self.session, SorbElement.separator)] + [m.si() for m in self.methodList]))))

class Client(Factory) :
    signature = 'client'

class Server(Factory)  :
    signature =  'server'

class ParseClassEntryError(RuntimeError) :
    pass

class SymbolNotFoundError(RuntimeError) :
    pass

class ParseMemberMethodError(RuntimeError) :
    pass

#parse initList like ['CT30', 'Login', ['User', 'bool'], []]
class MemberMethod(object) :
    def __init__(self, initList) :
        self.initList = initList
        if len(initList) == 3 :
            self.__id, self.__parameters, self.__returns = initList
            self.directive = ''
        elif len(initList) == 4 :
            self.directive, self.__id, self.__parameters, self.__returns = initList
        else :
            raise ParseMemberMethodError(str(initList))
        self.__parse_directive()

    def __parse_directive(self) :
        self.C = False
        self.S = False
        self.P = False
        self.L = True
        self.T = None
        #fixme, by default the function is local(no directive provided), the P and T and C and S is conflict with L.
        for d in 'CSBPT':
            if self.directive.count(d) == 1 :
                exec 'self.%s = True' % d
            elif self.directive.count(d) > 1 :
                raise ParseMemberMethodError(str(self.initList))
        if re.search('T(\d+)', self.directive) :
            self.T = int(re.search('T(\d+)', self.directive).groups()[0])
        if self.directive :
            self.L = False

    def id(self) :
        return self.__id

    def get_var_list(self, l):
        sum = lambda x,y: x+y
        r = set()
        for i in l :
            if len(i) == 0 :
                continue
            r |= set(reduce(sum, i))
        return list(r - set(['sequence']))

    def format_param(self, p):
        rtv = []
        for i in p:
            if len(i) == 1 :
                rtv.append(i[0])
            elif len(i) == 2 and i[0] == 'sequence':
                rtv.append('sequence<%s>' % i[1])
            else :
                raise ParseMemberMethodError(p)
        return rtv

    def get_returns(self) :
        return self.format_param(self.__returns)

    def get_parameters(self) :
        return self.format_param(self.__parameters)

    def get_required_symbols(self) :
        #parameter and returns are the list of lists, combine them and remove sequence directive
        return self.get_var_list([self.__returns, self.__parameters])

    @classmethod
    def get_instance(klass, initList) :
        if isinstance(initList[-1], list) and isinstance(initList[-2], list):
            return klass(initList)
        else :
            return None

    def si(self) :
        return '[%(directive)s] %(id)s(%(parameters)s)(%(returns)s); ' % dict(directive=self.directive, id=self.__id, parameters=', '.join(self.get_parameters()), returns=', '.join(self.get_returns()))


class Class(SorbElement) :
    signature = 'class'

    # remove all under score for every members?
    class ClassEntry(object) :
        def __init__(self, initList, order_counter) :
            self.__signature = None
            self.__type = None
            self.__identifier = None
            self.__order_counter = order_counter
            self.__default_value = None

            #like the following initList
            #['class', 'PhoneNumber', [[['string'], 'number'], [['PhoneType'], 'type', 'HOME']]]
            print 'init list is: ', initList
            if len(initList[0]) == 1 :
                self.__signature = 'optional'
                self.__type = initList[0][0]
            elif len(initList[0]) == 2 :
                if initList[0][0] != 'sequence' :
                    raise ParseClassEntryError('Unsupported class entry type %s' % initList[0][0])
                else :
                    self.__signature = 'repeated'
                    self.__type = initList[0][1]
            else :
                raise ParseClassEntryError('Unexpected class entry format %s' % initList[0])

            if len(initList) == 2 :
                self.__identifier = initList[1]

            if len(initList) == 3 :
                self.__identifier = initList[1]
                self.__default_value = initList[2]
        
        def type(self) :
            return self.__type
        
        def id(self) :
            return self.__identifier

        def value(self) :
            return self.__default_value
                
        def pb(self) :
            rtv = '%s %s %s = %s' % (self.__signature, self.__type, self.__identifier, self.__order_counter.increase())
            if self.__default_value:
                rtv += ' [default = %s]' % self.__default_value
            return rtv + SorbElement.separator;

        def si(self):
            rtv = '%s %s %s' % (self.__signature, self.__type, self.__identifier)
            if self.__default_value:
                rtv += ' = %s' % self.__default_value
            return rtv + SorbElement.separator;

        @classmethod
        def get_instance(klass, initList, order_counter=OrderCounter()) :
            if isinstance(initList[0], list) :
                return klass(initList, order_counter)
            else :
                return None

    def __init__(self, initList, parent) :
        SorbElement.__init__(self, initList, parent)
        self.__counter = OrderCounter()

        #fixme: split the memebers into two kind of groups may cause problem.
        #should put subtype above the class members, in case the class member
        #uses the subtype, then the protobuf may report a error, check later.

        self.__members = []
        self.__method_list = []
        # define the symbol keeper in the class
        self.symbol_keeper = TypeConsumer(parent.symbol_keeper)
        self.parse()

    def parse(self) :
        for i in self.values :
            m = Class.ClassEntry.get_instance(i, self.__counter) or \
                MemberMethod.get_instance(i)

            if not m :
                raise ParseClassEntryError('Unexpected class entry format %s' % i)

            if m.__class__ is Class.ClassEntry :
                self.symbol_keeper.check_symbol(m.id(), m.type(), m.value())
                self.__members.append(m)
            elif m.__class__ is MemberMethod :
                self.symbol_keeper.check_symbol(m.id(), 'func')
                #should check function's parameters syntax here?
                for s in m.get_required_symbols() :
                    #unresolved symbols should be defined in other modules, already kept in the idl's symbol_keeper
                    #resolve them when the current module is parsed
                    self.symbol_keeper.check_type(s)
                self.__method_list.append(m)


    def pb(self) :
        body = '%s' % ('\n'.join(SorbElement.shift([i.pb() for i in self.__members])))

        return '''\
%s {
%s
}
''' % ('message %s' % self.id(), body)

    def si(self) :
        body = '%s' % ('\n'.join(SorbElement.shift([i.si() for i in self.__members + self.__method_list])))

        return '''\
%s {
%s
}
''' % ('class %s' % self.id(), body)

class ParameterFormatError(RuntimeError) :
    pass

#fixme interface method default directive is C. class method default directive is L. server method directive is and only is L
class Interface(SorbElement) :
    signature = 'interface'

    def __init__(self, initList, parent) :
        SorbElement.__init__(self, initList, parent)
        self.__method_list=[]
        self.symbol_keeper = TypeConsumer(parent.symbol_keeper)

        for i in self.values :
            m = MemberMethod(i)
            self.__method_list.append(m)
            self.symbol_keeper.check_symbol(m.id(), 'func')
            #fixme, should check function's parameters here?
            #resolve them!
            for s in m.get_required_symbols() :
                #unresolved symbols should be defined in other modules, already kept in the idl's symbol_keeper
                #resolve them when the current module is parsed
                self.symbol_keeper.check_type(s)

    def si(self) :
        return '''\
%s {
%s
}
''' % (self.label(), '\n'.join(SorbElement.shift([i.si() for i in self.__method_list])))

    def cpp(self) :
        pass

    def python(self):
        pass

class CodeTypeError(RuntimeError) :
    pass

class ExtFileNameError(RuntimeError) :
    pass

class IDLFileNotFound(RuntimeError) :
    pass

class OutputDirError(RuntimeError) :
    pass

class ParseIDLError(RuntimeError) :
    pass

class SorbIDL(object) :
    def __init__(self, idl_file) :
        self.__idl_file = idl_file
        self.__languang_parser = SIParser()

        self.__element_list = []

        self.__check_parameter()

    def __check_parameter(self) :
        # locate IDL file
        self.__IDL_full_path = ''
        abs_path = os.path.abspath(self.__idl_file)
        # if the IDL file is the first to process,
        if os.path.isfile(abs_path) :
            self.__IDL_full_path = abs_path
        # the IDL file is just a imported name, without path and ext
        # the search dir should be used.
        else :
            abs_path = os.path.abspath(os.path.join(arg_parser.search, self.__idl_file + SORB_EXT))
            if os.path.isfile(abs_path) :
                self.__IDL_full_path = abs_path
            else :
                raise IDLFileNotFound('the IDL file %s can not be found' % abs_path)


        # now the self.__IDL_full_path should point to the real file with ext name '.si'
        # make sure the first IDL file to process ends with '.si'

        # remove ext name -- '.si'
        self.module_name = os.path.basename(self.__IDL_full_path)[0:-3]
        module_name = self.module_name
        # protobuf file ends with .proto
        self.pb_path = os.path.join(arg_parser.output_dir, module_name + PB_EXT)
        self.si_path = os.path.join(arg_parser.output_dir, module_name + INTERFACE_EXT)

        self.pb_include_filename = module_name + PB_HEADER_EXT
        self.pb_cpp_filename = module_name + PB_CPP_EXT
        self.interface_include_filename = module_name + INTERFACE_HEADER_EXT
        self.interface_cpp_filename = module_name + INTERFACE_CPP_EXT

    def type(self):
        return self.module_name

    def parse(self) :
        #add module to pool first, even it's not parsed. to deal with circle dependency import
        MODULES.append(self.module_name, self)

        #in case the multi-level imported module refer to the current one, we add it first.
        #define a toplevel symbol keeper, its parent is None
        self.symbol_keeper = TypeProvider()
        #self.extra_imports = []
        #self.__element_list = []
        parsed_result = self.__languang_parser.parse_str(file(self.__IDL_full_path, 'r').read())
        #parse imported module here. in order to avoid circular dependency, parse current module first,
        #left the unresolved symbols, resolve them again when the imported modules parsed.
        #this module first
        for i in parsed_result :
            print i
            m = Import.get_instance(i) or\
                Enum.get_instance(i, self) or\
                Class.get_instance(i, self) or\
                Interface.get_instance(i, self) or\
                Session.get_instance(i,self) or\
                Server.get_instance(i, self) or\
                Client.get_instance(i, self)

            if not m :
                # very rare, if it passed the EBNF parser, it should could be parsed successfully
                raise ParseIDLError('error occured when parsing segment %s' % i)

                self.symbol_keeper.add_type(m)

            self.__element_list.append(m)

        #add current module to pool, in case the later modules depends on it
        MODULES.append(self.module_name, self)

        #then imported modules
        for i in self.__element_list :
            if isinstance(i, Import) :
                i.parse()

        #then parse unresolved symbol which introduce by other modules
        self.symbol_keeper.resolve_imports()

    def get_message_header(self) :
        return self.pb_include_filename

    def get_message_source(self) :
        return self.pb_cpp_filename

    def get_interface_header(self) :
        return self.interface_include_filename

    def get_interface_source(self) :
        return self.interface_cpp_filename

    def write_IDL(self) :
        from StringIO import StringIO
        pb_sstrm = StringIO()
        si_sstrm = StringIO()

        pkg_directive = 'package %s;\n' % self.module_name
        pb_sstrm.write(pkg_directive)
        has_interface = False

        for i in self.__element_list :
            #write module name as package directive
            pb_sstrm.write(i.pb())
            si_sstrm.write(i.si())

            #if i.signature == 'import' :
            #    pb_sstrm.write(i.pb())
            #    si_sstrm.write(i.si())
            #if i.signature in ['enum', 'class'] :
            #    pb_sstrm.write(i.pb())
            #if i.signature == 'interface' :
            #    has_interface = True
            #    si_sstrm.write(i.si())

        pb_ofstream = file(self.pb_path, 'w')
        pb_ofstream.write(pb_sstrm.getvalue())
        pb_ofstream.close()

        interface_ostream = file(self.si_path, 'w')
        interface_ostream.write(si_sstrm.getvalue())
        interface_ostream.close()

    def gen_code(self) :
        import subprocess
        # for protobuf
        try :
            subprocess.check_output('protoc %(pb)s -I%(out_dir)s --%(language)s_out=%(out_dir)s' % dict(pb=self.pb_path, language=arg_parser.generate, out_dir=arg_parser.output_dir))
        except Exception, e:
            raise
            print str(e)
            sys.exit(1)
        else :
            print 'run protoc on %s successfully!' % self.__IDL_full_path

class SIParser(object) :
    def __initParser(self) :
        from pyparsing import  Word, alphas, alphanums, Regex, Suppress, Forward, Group, oneOf, ZeroOrMore, OneOrMore, Optional, delimitedList, Keyword, restOfLine, quotedString
        kwds = KEYWORDS
        for kw in kwds:
            exec("%s_ = Keyword('%s')" % (kw.upper(), kw))

        self.types = ' '.join(BUILTIN_TYPES)

        ident = ~oneOf(kwds) + Word(alphas + "_", alphanums + "_")
        integer = Regex(r"[+-]?\d+")
        listTemplate = Regex(r"sequence<[_a-zA-Z]+[_0-9a-zA-Z]*>")

        LBRACE,RBRACE,LBRACK,RBRACK,LPAR,RPAR,LABRACK,RABRACK,EQ,SEMI = map(Suppress,"{}[]()<>=;")

        callDirective = Regex(r"[CSBLP(T\d+)]+")

        classBody = Forward()

        classDefn = CLASS_ - ident + LBRACE + classBody + RBRACE

        typespec = oneOf(self.types) | ident | listTemplate
        dot_seperated_typespec = delimitedList(typespec, '.', combine=True)

        rvalue = integer | TRUE_ | FALSE_ | ident
        # fieldDefn ::= type ident {'=' rvalue}?
        fieldDefn = Group((SEQUENCE_ + LABRACK + dot_seperated_typespec + RABRACK) | dot_seperated_typespec ) + ident + Optional(EQ + rvalue) + SEMI

        # enumDefn ::= 'enum' ident '{' { ident ';' }* '}'
        enumDefn = ENUM_ - ident + LBRACE + Group(ZeroOrMore( ident + SEMI )) + RBRACE

        # methodDefn ::= rtv ident '(' {'out'}? typespec [',' {'out'}? typespec ]* ')';
        returnvalue = paravalue = Group(ZeroOrMore(delimitedList(Group((SEQUENCE_ + LABRACK + dot_seperated_typespec + RABRACK) | dot_seperated_typespec), ',')))
        methodDefn = Optional(LBRACK + callDirective + RBRACK) + ident + LPAR + paravalue + RPAR + LPAR + returnvalue + RPAR+ SEMI

        # classBody ::= { fieldDefn | enumDefn | classDefn }*
        classBody << Group(ZeroOrMore( Group(fieldDefn | methodDefn ) ))

        # interfaceDirective ::= 'interface' ident '{' methodDefn* '}'
        interfaceDirective = INTERFACE_ - ident + LBRACE + Group(ZeroOrMore(Group(methodDefn))) + RBRACE

        classDecl = Group(CLASS_ - ident + SEMI)
        interfaceDecl = Group(INTERFACE_ - ident + SEMI)
        sessionDecl = Group(SESSION_ - ident + SEMI)
        localMethodDefn = Group(ident + LPAR + paravalue + RPAR + LPAR + returnvalue + RPAR+ SEMI)

        sessionDirective = SESSION_ - ident + LBRACE + Group(ZeroOrMore( classDecl | interfaceDecl )) + RBRACE

        clientDirective = CLIENT_ - ident + LBRACE + Group(ZeroOrMore(sessionDecl | localMethodDefn)) + RBRACE

        serverDirective = SERVER_ - ident + LBRACE + Group(ZeroOrMore(sessionDecl | localMethodDefn)) + RBRACE

        comment = '#' + restOfLine

        # only support import module from the same directory for now
        # eg. "import m1, m2, m3;"
        importDirective = IMPORT_ - Group(delimitedList(Word(alphas+"_",alphanums+"_"), ',') + SEMI)

        topLevelStatement = Group(classDefn | enumDefn | interfaceDirective | sessionDirective | clientDirective | serverDirective)

        self.parser = ZeroOrMore(Group(importDirective)) + ZeroOrMore(topLevelStatement)

        self.parser.ignore(comment)

    def __init__(self) :
        self.__initParser()

    def parse_str(self, si_str) :
        return self.parser.parseString(si_str, parseAll=True).asList()

if __name__ == '__main__' :
    arg_parser.parse()
    idl = SorbIDL(arg_parser.file)
    idl.parse()
    MODULES.gen_code()
    
#    parser = SIParser()
#    import pprint
#    pprint.pprint(parser.parse_str(file('../si/user.si', 'rb').read()))
    






